#pragma once
#include "ChainingModeEncryption.h"
#include "ChainingModeDecryption.h"
#include "Encryptor.h"
#include "Decryptor.h"
#include "LastBlockEncryption.h"
#include "LastBlockDecryption.h"

namespace AppliedCryptography
{
	namespace AC = AppliedCryptography;

	template<unsigned int TKeySize, unsigned int TBlockSize, class TCryptor, class TLBHandle>
	class CBC
	{
	public:
		class Encryption : public ChainingModeEncryption<TKeySize, TBlockSize, TCryptor, TLBHandle>
		{
		public:
			virtual void Process(const TCryptor& cryptor,
				const TLBHandle& lbHandler,
				const Block<TBlockSize>& iv, 
				const BlockData<TBlockSize>& input, 
				BlockData<TBlockSize>& output) const
			{
				const AC::Encryptor<TKeySize, TBlockSize>& baseCryptor = cryptor;
				const AC::LastBlockEncryption<TKeySize, TBlockSize>& baseLbHandler = lbHandler;

				Block<TBlockSize> xored;
				Block<TBlockSize> chyper;
				Block<TBlockSize> secondToLast;
				Block<TBlockSize> last;

				chyper = iv;

				if(input.Blocks() > 1)
				{
					unsigned int i = 0;
					while(i < (input.Blocks() - 2) )
					{
						xored = input[i] ^ chyper;
						baseCryptor.Process(xored, chyper);
						output.Add(chyper, TBlockSize);
						++i;
					};
				};

				if(input.Blocks() > 1)
				{
					xored = input[input.Blocks() - 2] ^ chyper;
					baseCryptor.Process(xored, secondToLast);
				}
				else
				{
					secondToLast = iv;
				};

				last = baseLbHandler.Process(baseCryptor, input[input.Blocks()-1], input.LastBlockLength(), secondToLast);

				output.Add(secondToLast, TBlockSize);

				if(input.Blocks() > 1)
				{
					output.Add(last, input.LastBlockLength());
				};
			};
		};

		class Decryption : public ChainingModeDecryption<TKeySize, TBlockSize, TCryptor, TLBHandle>
		{
		public:
			virtual void Process(const TCryptor& cryptor,
				const TLBHandle& lbHandler,
				const Block<TBlockSize>& iv, 
				const BlockData<TBlockSize>& input, 
				BlockData<TBlockSize>& output) const
			{
				const AC::Decryptor<TKeySize, TBlockSize>& baseCryptor = cryptor;
				const AC::LastBlockDecryption<TKeySize, TBlockSize>& baseLbHandler = lbHandler;

				Block<TBlockSize> xored;
				Block<TBlockSize> plainXor;
				Block<TBlockSize> secondToLast;
				Block<TBlockSize> last;

				xored = iv;

				if(input.Blocks() > 1)
				{
					unsigned int i = 0;
					while(i < (input.Blocks() - 2) )
					{
						baseCryptor.Process(input[i], plainXor);
						xored ^= plainXor;
						output.Add(xored, TBlockSize);
						xored = input[i];
						++i;
					};
				};

				if(input.Blocks() > 1)
				{
					baseCryptor.Process(input[input.Blocks() - 2], plainXor);
				}
				else
				{
					plainXor = iv;
				};

				last = baseLbHandler.Process(baseCryptor, input[input.Blocks()-1], input.LastBlockLength(), plainXor);

				secondToLast = xored ^ plainXor;
				output.Add(secondToLast, TBlockSize);

				if(input.Blocks() > 1)
				{
					output.Add(last, input.LastBlockLength());
				};
			};
		};
	};
};
